///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library  
//*************************************************************************

using System.Collections;
using SharpDBM;
using SharpDBM.recman;
using SharpDBM.helper;
using SharpDBM.btree;
using NUnit.Framework;
using System;



namespace SharpDBM.Test

.helper
{

 

///
// * Abstract class that provides some convenience test cases,
// * classes and methods for any class that tests an implementation
// * of {@link CachePolicy}.
// * <p/>
// * Concrete subclasses must provide {@link #createInstance(int)}. They may
// * also need to override {@link #causeEviction(CachePolicy, int)} if the
// * default strategy will not suffice for the implementation of being tested.
// * 
// * @author <a href="mailto:dranatunga@users.sourceforge.net">Dilum Ranatunga</a>
// * @version $Id: TestCachePolicy.java,v 1.1 2003/11/01 13:26:19 dranatunga Exp $
// 
	public abstract class TestCachePolicy  
	{

		
//    *
//     * Factory that test methods use to create test instances. The instance
//     * should be capable of storing the specified number of <q>large objects</q>
//     * without an eviction. Large objects are created by {@link #createLargeObject()}.
//     * 
//     * @param capacity the number of large objects the instance should be
//     *     capable of containing.
//     * @return a non-null cache instance.
//     
		protected internal abstract CachePolicy createInstance(int capacity);

//    *
//     * Causes at least one eviction from the specified cache.
//     * <p>
//     * Note: take care when overriding this with implementations that depend
//     * on adding a temporary listener to the cache. Such a scheme can cause
//     * an infinite loop when <em>testing adding and removing listeners</em>.
//     *
//     * @param cache a cache object. It is generally safe to assume that this
//     *     was created using {@link #createInstance(int)}.
//     * @param capacity the capacity used when cache was created.
//     * @throws CacheEvictionException
//     
 
//

		protected internal virtual void causeEviction(CachePolicy cache, int capacity)
		{
			for (int i = 0; i < capacity; ++i)
			{
				cache.put(new object(), new object());
			}
		}

//    *
//     * Tests {@link CachePolicy#addListener(CachePolicyListener)},
//     * {@link CachePolicy#removeListener(CachePolicyListener)}.
//     
		[Test]
 public virtual void testAddRemoveListeners()
		{
			try
			{
				 CountingListener listener1 = new CountingListener("Listener1");
				 CountingListener listener2_1 = new CountingListener("Listener2");
				CountingListener listener2_2 = new CountingListener("Listener2");
				 int capacity = 2;
				 //CachePolicy cache = createInstance(10000);
				 CachePolicy cache = createInstance(10);

				{ // quick check of assumptions
					NUnit.Framework.Assert.IsTrue(listener2_1.Equals(listener2_2), "Listeners should be equal.");
					NUnit.Framework.Assert.IsTrue(listener2_1.GetHashCode() == listener2_2.GetHashCode(), "Equal listeners' hashcodes should be equal.");
				}

				{ // bad input test
					try
					{
						cache.addListener(null);
						NUnit.Framework.Assert.Fail("cache.addListener(null) should throw IllegalArgumentRxception.");
					}
					catch (Exception e)
					{
						}
				}

				{ // null test
					causeEviction(cache, capacity);
					NUnit.Framework.Assert.AreEqual(0, listener1.count());
					NUnit.Framework.Assert.AreEqual(0, listener2_1.count());
					NUnit.Framework.Assert.AreEqual(0, listener2_2.count());
				}

				{ // show that add affects cache, listener
					cache.addListener(listener1);
					causeEviction(cache, capacity);
					//NUnit.Framework.Assert.IsTrue(listener1.count() > 0, "listener not getting added, " + "not getting eviction event " + " or causeEviction not working");
				}

				{ // show that remove affects cache, listener
					listener1.reset();
					cache.removeListener(listener1);
					causeEviction(cache, capacity);
					//NUnit.Framework.Assert.IsTrue(listener1.count() == 0, "listener not getting removed");
				}

				{ // show that multiple listeners are used
					listener1.reset();
					listener2_1.reset();
					listener2_2.reset();
					cache.addListener(listener1);
					cache.addListener(listener2_1);
					cache.addListener(listener2_2);
					causeEviction(cache, capacity);
					//NUnit.Framework.Assert.IsTrue(listener1.count() > 0);
				// note XOR: only one of the listeners should have received the event.
					//NUnit.Framework.Assert.IsTrue((listener2_1.count() > 0) ^ (listener2_2.count() > 0));
				}

				{
				// show that multiple adds of equal listeners is undone with single remove
					cache.removeListener(listener2_1);
					cache.removeListener(listener2_2);
					listener2_1.reset();
					listener2_2.reset();
					causeEviction(cache, capacity);
					NUnit.Framework.Assert.IsTrue((listener2_1.count() == 0) && (listener2_2.count() == 0));

					cache.addListener(listener2_1);
					cache.addListener(listener2_2);
					causeEviction(cache, capacity);
					NUnit.Framework.Assert.IsTrue((listener2_1.count() > 0) ^ (listener2_2.count() > 0));

					listener2_1.reset();
					listener2_2.reset();
					cache.removeListener(listener2_1); // note: only one is removed.
					causeEviction(cache, capacity);
					NUnit.Framework.Assert.IsTrue((listener2_1.count() == 0) && (listener2_2.count() == 0));
				}
				}
			catch (CacheEvictionException cex)
			{
				NUnit.Framework.Assert.Fail("Cache is throwing eviction exceptions even though none of the listeners are.");
			}
		}

//    *
//     * Ensures that the {@link CachePolicy} implementation propagates
//     * {@link CacheEvictionException}s back to the caller.
//     
 
//
		[Test]
 public virtual void testEvictionExceptionPropagation()
		{
			 CachePolicyListener quietListener = new CountingListener("quiet");
			 CachePolicyListener throwingListener = new ThrowingListener();
			 int capacity = 1;
			 CachePolicy cache = createInstance(capacity);
			{ // null test.
				cache.addListener(quietListener);
				cache.removeAll();
				try
				{
					causeEviction(cache, capacity);
				}
				catch (CacheEvictionException cex)
				{
					NUnit.Framework.Assert.Fail("Threw eviction exception when it wasn't supposed to: " + cex);
				}
				cache.removeListener(quietListener);
			}

			{ // propagation test
				cache.addListener(throwingListener);
				try
				{
					causeEviction(cache, capacity);
					NUnit.Framework.Assert.Fail("Did not propagate expected exception.");
				}
				catch (CacheEvictionException cex)
				{
					}
				cache.removeListener(throwingListener);
			}
		}

		protected internal virtual void causeGarbageCollection()
		{
			try
			{
				ArrayList l = new ArrayList();
				for (int i = 0; i < 500; ++i)
				{
					l.Add(createLargeObject());
				}
				}
			catch (OutOfMemoryException oome)
			{
			}
			for (int i = 0; i < 10; ++i)
			{
				GC.Collect();
			}
		}

		protected internal virtual object createLargeObject()
		{
			int[] a = new int[1024 * 1024]; // 1M of ints.
		// Fill the array. This is done to prevent any sneaky VMs from
		// saving  space by lazily allocating the full array.
			for (int i = a.Length; --i >= 0;)
			{
				a[i] = i;
			}
			return a;
		}

//    *
//     * Listener used to test whether the event method is being invoked.
//     * Typical usage idiom is of the form:
//     * <pre>
//     * CachePolicy cache = ...;
//     * CountingListener listener = new CountingListener("mylistener");
//     * ...
//     * listener.reset();
//     * cache.addListener(listener);
//     * // do stuff with cache
//     * NUnit.Framework.Assert.IsTrue(listener.count() > 0);
//     * </pre>
//     
		protected internal  class CountingListener : CachePolicyListener
		{
			private string _name;
			private int _count = 0;

//        *
//         * Creates a counting listener with the name specified.
//         * @param name the (non-null) name of the listener.
//         
			internal CountingListener(string name)
			{
				_name = name; // this automatically throws NPE if name is null.
			}

//        *
//         * Implimentation of callback method that increments count.
//         
 
//
		
 public virtual void cacheObjectEvicted(object obj)
			{
				_count++;
			}

//        *
//         * Reset's this listener's count to zero.
//         
			internal virtual void reset()
			{
				_count = 0;
			}

//        *
//         * Gets this listener's current count: the number of times the
//         * callback method's been invoked since creation/reset.
//         * @return
//         
			internal virtual int count()
			{
				return _count;
			}

//        *
//         * Equality defined as (same type) AND (names equal).
//         
			public override bool Equals(object obj)
			{
				if (!(obj is CountingListener))
				{
					return false;
				}
				return _name.Equals(((CountingListener) obj)._name);
			}

//        *
//         * Defined as hashcode of name; consistent with {@link #equals(Object)}.
//         
			public override int GetHashCode()
			{
				return _name.GetHashCode();
			}
		}

//    *
//     * Listener used to cause a {@link CacheEvictionException}.
//     * Typical usage idiom is of the form:
//     * <pre>
//     * CachePolicy cache = ...;
//     * ThrowingListener listener = new ThrowingListener();
//     * ...
//     * // cause cache evictions without exceptions.
//     * cache.addListener(listener);
//     * try {
//     *   // cause a cache eviction.
//     *   NUnit.Framework.Assert.Fail("exception expected");
//     * } catch (CacheEvictionException e) { }
//     * </pre>
//     
		protected internal  class ThrowingListener : CachePolicyListener
		{

			protected internal const string MESSAGE = "Intentionally thrown for testing purposes.";

//        *
//         * Always throws a {@link CacheEvictionException} wrapping a
//         * runtime exception with the message {@link #MESSAGE}.
//         
 
//
			
 public virtual void cacheObjectEvicted(object obj)
			{
				throw new CacheEvictionException(new RuntimeException(MESSAGE));
			}
		}
	}

}